#!/usr/bin/env python
"""
Acorn-SDR ('A Collection of Radio Nodes')
(c) Bob Cowdery (G3UKB) 2009

File: outstr.py
Date: January 2009

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

The author can be reached by email at

bob@g3ukb.co.uk

================================================================================
Module Description:

Output streaming.
Note: Jack is started in the startup script and can only be started with one set
of parameters.

When running with HPSDR as a remote output the output should be fixed (48K/512)
When running with SDR1000/Softrock  the output should not be fixed so it follows the input.

"""

#-------------------------------------------------------------------------------
# System imports
import sys, traceback, Ice
import jack

from Numeric import *
from time import sleep

FRAMES = 512

#===============================================================================
class AudioOut():
    
    #---------------------------------------------------------------------------
    def __init__(self, ch1, ch2, rate, frames, fixed):  
        self.ch1 = ch1
        self.ch2 = ch2
        self.rate = rate
        self.actual_frames = int(frames)
        self.fixed = fixed
        self.frames = self.set_frames(rate, frames)
        self.set_buffers(self.frames, self.actual_frames)
        self.output_on = False
        self.updates = {}
    
    #---------------------------------------------------------------------------
    def set_frames(self, rate, frames):
        if self.fixed:
            # Fix the output rate at 48K as this is what we expect to get
            if rate == 96000:
                return frames/2
            elif rate == 192000:
                return frames/4
            else:
                return frames
        else:
            # The output rate follows the actual rate set
            return frames
    
    #---------------------------------------------------------------------------
    def set_buffers(self, frames, actual_frames):
        if self.fixed:
            # Fix the frames at 512 and adjust the writes accordingly
            self.capture = zeros((2, FRAMES), 'f')
            self.out = zeros((2, FRAMES), 'f')
            self.counter = 0
            # Work out how many writes/buffers to send the data
            self.frame_cnt = FRAMES/frames
            if self.frame_cnt == 0:
                # We will receive a larger buffer than we send in each write
                self.frame_cnt = -1
        else:
            # Set buffers to the actual frame size
            self.capture = zeros((2, actual_frames), 'f')
            self.out = zeros((2, actual_frames), 'f')
        
    #---------------------------------------------------------------------------
    def stream_on(self):
        jack.attach("acornout")
        jack.register_port("in_1", jack.IsInput)
        jack.register_port("in_2", jack.IsInput)
        jack.register_port("out_1", jack.IsOutput)
        jack.register_port("out_2", jack.IsOutput)
        jack.activate()
        jack.connect("acornout:out_1", self.ch1)
        jack.connect("acornout:out_2", self.ch2)
        self.output_on = True

    #---------------------------------------------------------------------------
    def stream_off(self):
        if self.output_on == True:
            jack.disconnect("acornout:out_1", self.ch1)
            jack.disconnect("acornout:out_2", self.ch2)
            jack.deactivate()
            jack.detach()
            self.output_on = False
    
    #---------------------------------------------------------------------------
    def output(self, str):
        if not self.output_on: return None
         
        if self.fixed:
            try:
                # Exchange the streams
                out = asarray(str, 'f')
                 # We might have just changed buffer size and got a residual buffer
                if int(self.frames) <>  len(out[0]):
                    print 'OutStr Service: (Received frame of ', len(out[0]), ', expected ', self.frames, '. Ignore if you did a live change)'
                    self.counter = 0
                    return
                if self.frame_cnt > 0:
                    # See if we have enough to output
                    #print 'Length: ', len(self.out[0]), len(self.capture[0]), len(out[0]), self.counter*self.frames, self.counter, self.frames
                    self.out[0][self.counter*self.frames:(self.counter*self.frames)+self.frames] = out[0]
                    self.out[1][self.counter*self.frames:(self.counter*self.frames)+self.frames] = out[1]
                    self.counter += 1
                    if self.counter == self.frame_cnt:
                        #print 'Counts: ', self.counter, self.frame_cnt
                        jack.process(self.out, self.capture)
                        self.counter = 0
                else:
                    # The buffer is larger than FRAME size. At the moment this means 1024
                    # therefore split into two writes.
                    self.out[0] = out[0][:FRAMES]
                    self.out[1] = out[1][:FRAMES]
                    jack.process(self.out, self.capture)
                    sleep(0.01)
                    self.out[0] = out[0][FRAMES:]
                    self.out[1] = out[1][FRAMES:]
                    jack.process(self.out, self.capture)
                    
            except jack.InputSyncError:
                print "Jack - Input Sync Error"
                self.counter = 0
            except jack.OutputSyncError:
                print "Jack - Output Sync Error"
                self.counter = 0
        else:
            try:
                # Exchange the streams
                out = asarray(str, 'f')
                 # We might have just changed buffer size and got a residual buffer
                if int(self.frames) <>  len(out[0]):
                    print 'OutStr Service: (Received frame of ', len(out[0]), ', expected ', self.frames, '. Ignore if you did a live change)'
                    return
                jack.process(self.out, self.capture)
                    
            except jack.InputSyncError:
                print "Jack - Input Sync Error"
            except jack.OutputSyncError:
                print "Jack - Output Sync Error"

    #-----------------------------------------------------------------------
    def set_audiorate(self, value):
        self.updates['audio_rate'] = (value, self.set_audiorate_1, True)
    
    #-----------------------------------------------------------------------
    def set_audiorate_1(self, value):
        self.rate = value
        self.frames = self.set_frames(value, self.actual_frames)
        
    #-----------------------------------------------------------------------
    def set_audioframes(self, value):
        self.updates['audio_frames'] = (value, self.set_audioframes_1, True)
        
    #-----------------------------------------------------------------------
    def set_audioframes_1(self, value):
        self.actual_frames = value
        self.frames = self.set_frames(self.rate, value)
        
    #-----------------------------------------------------------------------
    def apply_updates(self):
        # This must be called at the end of a cycle so we don't apply updates
        # when the outstr is running
        # Let this run straight away as we will match any stray buffers in the pipeline
        # by sending an empty response of the correct size
        # ToDo: We should have a mutex around here as there is a window
        for key in self.updates.keys():
            if self.updates[key][2]:
                was_on = False
                if self.output_on:
                    was_on = True
                    self.stream_off()
            self.updates[key][1](self.updates[key][0])
            # Reinitialise
            if self.updates[key][2]:
                self.set_buffers(self.frames, self.actual_frames)
                if was_on:
                    self.stream_on()                    
        self.updates.clear()